////////////////////////////////////////////////
// DESCRIPTION:
//    Support classes for creating MSI Custom Actions
//
// Legal Notices:
//   Copyright (c) 2008, Telliam Consulting, LLC.
//   All rights reserved.
//
//   Redistribution and use in source and binary forms, with or without modification,
//   are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice, this list
//     of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice, this
//     list of conditions and the following disclaimer in the documentation and/or other
//     materials provided with the distribution.
//   * Neither the name of Telliam Consulting nor the names of its contributors may be
//     used to endorse or promote products derived from this software without specific
//     prior written permission.
//
//   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
//   EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
//   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
//   SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
//   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
//   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
//   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
//   ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
//   DAMAGE. 
//

#pragma once

#include <vector>
#include <atlstr.h>
#include <comutil.h>

namespace FusionWare
{
    namespace MSI
    {
        //exception type for MSI errors
        class Exception
        {
        public:
            ATL::CString Msg;
            UINT Error;

            Exception(const TCHAR* Msg, UINT Error) 
                : Msg(Msg)
                , Error(Error)
            {
            }
        };

        static const std::bad_alloc OutOfMemory;

        inline void ThrowOnFailure(UINT Err, const TCHAR* ErrMsg)
        {
            if(ERROR_SUCCESS != Err)
                throw FusionWare::MSI::Exception(ErrMsg, Err);
        }

        inline void ThrowLastErrorOnFailure(BOOL Condition, const TCHAR* ErrMsg)
        {
            if(!Condition)
                throw FusionWare::MSI::Exception(ErrMsg, HRESULT_FROM_WIN32(GetLastError()));
        }
        
        // MSI table record
        class Record 
        {
            PMSIHANDLE hRecord;
        public:
            Record(UINT FieldCount)
                : hRecord(::MsiCreateRecord(FieldCount))
            {
            }

            Record(MSIHANDLE hRecord)
                : hRecord(hRecord)
            {
            }
 
            Record()
                : hRecord(NULL)
            {
            }
            operator MSIHANDLE() { return MSIHANDLE(hRecord); }
            operator MSIHANDLE*() { return &hRecord; }

            void GetField(UINT FieldIndex, int& Value, int DefaultValue = 0);
            void GetField(UINT FieldIndex, ATL::CString& Value, bool DefaultSpace = true);

            void SetField(UINT FieldIndex, ATL::CString& Value);
            void SetField(UINT FieldIndex, int Value);
        };

        typedef srutil::delegate<void (Record&)> RecordHandler;

        // MSI database View
        class View 
        {
            PMSIHANDLE hView;
        public:
            View(MSIHANDLE hView)
                : hView(hView)
            {
            }
            
            void ForEachRecord(RecordHandler Action);

            std::auto_ptr<Record> Fetch();
        };
        
        // logfile for storing data used across multiple CA calls
        // since MSI will completely unload the DLL after each call
        // static data won't work. This is typically used to store
        // information used to roll back actions in the event of a
        // later failure
        class CALogFile
        {
            HANDLE hFile;
            CPath Path;
            bool DeleteOnClose;

        public:
            CALogFile()
                : hFile(NULL)
                , DeleteOnClose(false)
            {
            }

            ~CALogFile()
            {
                Close();
            }

            void Create(const CPath& Path);
            void Open(const CPath& Path, bool DeleteOnClose = false);
            void Close();
            void Write(const CString& Txt );
            CString Read();

        private:
            // disable assignment and copy constructor
            CALogFile(const CALogFile&);
            CALogFile& operator=(const CALogFile&);
        };

       
        // MSI session
        class Session 
        {
            // don't use PMSIHANDLE here so the handle isn't
            // auto closed. MSI engine owns this handle custom
            // actions just rent it. 
            MSIHANDLE hInstall;
            ATL::CString LogName;
            PMSIHANDLE hDataBase;

        public:
            Session(const TCHAR* LogName, MSIHANDLE hInstall)
                : LogName(LogName)
                , hInstall(hInstall)
            {
                DWORD err;
                this->hDataBase = ::MsiGetActiveDatabase(this->hInstall);
                if(0 == this->hDataBase)
                    err = ::GetLastError();
            }

            ////////////////////////////////////////////////////////////
            // Description:
            //    Logs a message to the MSI logging system
            //
            // Input:
            //    Msg - Message to log
            //    ThrowOnFail - [default: true] If true this method will
            //                  throw an exception on failure (see remarks)
            //
            // Remarks:
            //    Logs the message to the MSI engines current log. If an
            //    error occurs while trying to create the log message and
            //    ThrowOnFail is true then an excpetion is thrown, otherwise
            //    it is not. ThrowOnFail is true by default and is normally
            //    only set to false when logging that an error or exception
            //    occured. This prevents an exception from being thrown and
            //    logged while logging a previous failure, which could
            //    otherwise be an infinite recursion of exceptions...
            //    
            void LogMsgNoThrow(const TCHAR* Msg);
            void LogMsg(const TCHAR* Fmt, ...);

            UINT ProcessMessage(INSTALLMESSAGE MessageType, Record MessageRecord);
            void ExtendProgressBar(UINT Cost);

            CPath GetTargetPath(const TCHAR* DirName);
            CPath GetFileInstallPath(const TCHAR* File);
            CString GetFileName(const TCHAR* FileId);

            ////////////////////////////////////////////////////////////
            // Description:
            //   Retrieves and parses the CustomActionData to a vector
            //
            // Input:
            //   MinArgs    - [Default=0] Minumum number of arguments
            //                 expected.
            //   pDelimiter - [Default=_T(";")] Delimiter between args
            //                in the CustomActionData string.
            //
            // Returns:
            //   Vector of strings - one arg per entry.
            // 
            // Remarks:
            //   The arguments are provided in the MSI CustomActionData
            //   property. They *MUST* be formatted with the delimiter.
            //   This function will retrieve the data as a single string
            //   then parse it into the individual sub strings.
            //
            //   If MinArgs is greater then the total number of arguments
            //   parsed this method will throw an exception.
            //
            std::vector<ATL::CString> GetCustomActionData(unsigned MinArgs=0, const TCHAR* pDelimiter=_T(";"));

            std::vector<CString> SplitString(const CString& String, const TCHAR* pDelimiter);

            ////////////////////////////////////////////////////////////
            // Description:
            //   Delegate for processing a custom action
            //
            // Remarks:
            //   Functions implementing this signature should throw an
            //   MSI::Exception on errors
            //
            typedef srutil::delegate<void ()> CustomAction;

            ////////////////////////////////////////////////////////////
            // Description:
            //   Process a single action
            //
            // Returns:
            //   MSI error/Status code
            //
            // Remarks:
            //   Process a single Custom action by calling the provided
            //   Action function in a try/catch block to translate
            //   exceptions to proper return codes for MSI
            //   
            UINT DoCustomAction(CustomAction Action);

            ////////////////////////////////////////////////////////////
            // Description:
            //   Get the value of a property from this session
            //
            // Returns:
            //   String value of the property
            //
            ATL::CString GetProperty(const TCHAR* Name);
            
            ////////////////////////////////////////////////////////////
            // Description:
            //   Get the value of a property from this session
            //
            // Returns:
            //   String value of the property
            //
            void SetProperty(const TCHAR* Name, const TCHAR* Value);

            ////////////////////////////////////////////////////////////
            // Description:
            //   Creates and view and executes the provided query 
            //
            // Input:
            //   MSI SQL Query string to execute
            //
            // Returns:
            //   new view based on the provided query
            //
            std::auto_ptr<View> CreateAndExecuteView(CString, ...);
            
            ////////////////////////////////////////////////////////////
            // Description:
            //   Schedules a deferred custom action for later execution
            //
            // Input:
            //   Name - Name of the custom action in the CUSTOMACTION table
            //   CustomActionData - delimited string containing params for the action
            //   Cost - progress bar tick cost for this action
            //
            void ScheduleDeferredAction(const TCHAR* Name, const TCHAR* CustomActionData = NULL, UINT Cost = 0);

            enum LogAction
            {
                LogActionNone,
                LogActionInstall,
                LogActionUninstall,
            };

            enum LogType
            {
                LogTypeScheduled,
                LogTypeRollBack,
            };

            ATL::CPath CreateCustomActionLogName(LogAction Action, LogType Type, bool Impersonated);

            bool IsScheduled()
            {
                return ::MsiGetMode(this->hInstall, MSIRUNMODE_SCHEDULED) ? true : false;
            }

            bool IsRollBack()
            {
                return ::MsiGetMode(this->hInstall, MSIRUNMODE_ROLLBACK) ? true : false;
            }

            bool IsCommit()
            {
                return ::MsiGetMode(this->hInstall, MSIRUNMODE_COMMIT) ? true : false;
            }
        };
    }
}
